﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using RayDen.Library.Components;
using RayDen.Library.Components.Surface;
using RayDen.Library.Components.Surface.Spectral;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data;
using RayDen.Library.Entity.Interface;
using RayDen.Library.Entity.Scene;
using RayDen.RayEngine.Core.Components;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Library;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Core.Types.Volume;
using RayDen.RayEngine.Data;
using RayDen.RayEngine.Samplers;
using RayDen.RayEngine.Scene;

namespace RayDen.RayEngine
{
    public class RayEngineScene : IRayEngineScene
    {
        private RgbSpectrumInfo skyEmission = new RgbSpectrumInfo(0f, 0f, 0.009f), groundReflection = new RgbSpectrumInfo(0.06f);
        private bool noMeshLights;
        public bool AreaLightAsMesh = true;
        public TextureSamplingQuality TextureSamplingQuality = TextureSamplingQuality.Nearest;

        public SceneGeometryInfo SceneGeometry;
        public IMaterialProvider MaterialProvider;
        public IMaterialLibrary<ISurfaceMaterial> MatLib;
        public List<VolumeMaterialInfo> VolumeMats;
        public OptimizedTextureAtlas OptTextures;
        internal Dictionary<TextureType, int>[] texturesMap;
        internal Dictionary<TextureType, int>[] materialTextureMap;
        internal Dictionary<string, int> texMaps;

        public SpectralDataCache<SampledSpectrum> sampledSpectrumCache;
        public SpectralDataCache<SpectralDistribution> spdCache;


        public SceneCache Cache;

        protected PrimitiveCache primitives;

        public ILight[] Lights;
        //public List<TriangleInfo> Triangles;
        public TriangleDataInfo[] Triangles;
        public ITriangleMesh[] Meshes;
        private List<ITriangleMesh> meshes;
        public List<ICamera> Cameras;
        public List<MeshVertex> MeshVertices;

        public Point[] Vertices;

        public VolumeIntegrator VolumeIntegrator;
        public int ShadowRayCount { get; set; }
        public int MaxPaths { get; set; }
        public int MaxPathDepth;
        public float RussianRuletteImportanceCap;
        public RgbSpectrum DefaultLightGain { get; set; }
        public BaseCamera Camera;
        public BaseInfiniteLight EnvironmentMap;

        internal SceneDataInfo sceneData;
        TriangleLight[] triLights;
        HashSet<int> lightIndex;
        HashSet<int> volumeIndex;
        private bool noVolumes;

        public LightSamplingStrategy LightSamplingStrategy;


        public int ShadowRaysPerSample
        {
            get
            {
                switch (LightSamplingStrategy)
                {
                    case LightSamplingStrategy.UniformAllLights:
                        return this.Lights.Length * this.ShadowRayCount;
                    case LightSamplingStrategy.UniformOneLight:
                        return this.ShadowRayCount;
                    case LightSamplingStrategy.WeightedOneLight:
                        return this.ShadowRayCount;
                    case LightSamplingStrategy.Unknown:
                        return this.ShadowRayCount;
                }
                return this.ShadowRayCount;
            }
        }

        public RayEngineScene()
        {
            this.DefaultLightGain = new RgbSpectrum(1.75f);
            this.MaxPathDepth = 5;
            this.RussianRuletteImportanceCap = 0.65f;
            this.MaxPaths = 65536 >> 4;
            this.ShadowRayCount = 1;
        }

        private ITriangleMesh[] lightMeshes;

        internal void BuildSceneData()
        {
            if (this.sceneData == null)
            {
                this.sceneData = new SceneDataInfo(SceneGeometry);

            }
            this.Vertices = SceneGeometry.Vertices;
            this.Triangles = sceneData.Triangles.ToArray();
            this.meshes = new List<ITriangleMesh>();
            this.meshes.AddRange(sceneData.Meshes);
            this.Meshes = meshes.ToArray();
            this.volumeIndex = new HashSet<int>();
            this.VolumeMats = new List<VolumeMaterialInfo>();
            foreach (var triangleMesh in Meshes)
            {
                if (VolumeSampler.IsVolume(triangleMesh))
                {
                    for (int i = triangleMesh.StartTriangle; i < triangleMesh.EndTriangle; i++)
                    {
                        volumeIndex.Add(i);
                    }
                    triangleMesh.MeshProfile = new VolumeProfile() { };
                }
            }
            noVolumes = !volumeIndex.Any();
            /*
            var da = new BvhDataAdapter(this);

            foreach (var triangleMesh in Meshes.Cast<TriangleMeshInfo>())
            {
               triangleMesh.MeshProfile.BvhData = da.BuildData(triangleMesh);
            }

            var bounds = meshes.Cast<TriangleMeshInfo>().Select(item => item.Bounds).ToArray();

            var topLevelData = da.BuildTopData(bounds);
            Tracer.TraceLine("TopLevel data", topLevelData.Length);
            */
            if (Lights != null)
            {
                if (!Lights.OfType<MeshLight>().Any())
                {
                    foreach (var triangleMesh in Meshes)
                    {
                        if (triangleMesh.MeshProfile is LightsourceProfile)
                        {
                            triangleMesh.MeshProfile = new SurfaceProfile() { MaterialName = triangleMesh.MaterialName };
                        }
                    }
                }

                if (AreaLightAsMesh)
                {
                    Tracer.TraceLine("Area Lights treated as meshes");

                    foreach (var lightsource in Lights.OfType<MeshLight>())
                    {
                        lightsource.Initialize(this);
                    }
                }
                else
                {
                    var triL = new List<TriangleLight>();

                    Tracer.TraceLine("Area Lights treated on triangle basis");
                    var meshLights = Lights.OfType<MeshLight>().ToArray();

                    foreach (var meshLight in meshLights)
                    {
                        MeshLight light = meshLight;
                        var mesh = Meshes.FirstOrDefault(item => item.MeshName.Equals(light.LightName, StringComparison.InvariantCultureIgnoreCase));
                        if (mesh != null)
                        {
                            for (int st = mesh.StartTriangle; st < mesh.EndTriangle; st++)
                            {
                                triL.Add(new TriangleLight(this, st, mesh, light.gain.IsBlack() ? RgbSpectrum.UnitSpectrum() : (RgbSpectrum)light.gain) { Profile = meshLight.Profile });
                            }
                        }
                    }
                    this.triLights = triL.ToArray();
                    lightIndex = new HashSet<int>(triLights.Select(item => item.triangleIndex));
                    this.Lights = this.Lights.Except(meshLights).Union(triLights).ToArray();
                }
                noMeshLights = !(this.Lights.Any(item => item is MeshLight || item is TriangleLight));
            }

            lightMeshes = this.Lights.Where(m => m is MeshLight).Cast<MeshLight>().Select(ml => ml.mesh).ToArray();
            /*
            texMaps = new Dictionary<string, int>();
            int index = 0;
            foreach (var elem in texturesMap)
            {
                var mat = MatLib.GetSurfMat(index).Name;
                if (!texMaps.ContainsKey(mat))
                {
                    texMaps.Add(mat, index);
                    index++;
                }
            }*/
        }


        public void InitTextures()
        {
            texMaps = new Dictionary<string, int>();
            int index = 0;
            foreach (var elem in texturesMap)
            {
                var mat = MatLib.GetSurfMat(index).Name;
                if (!texMaps.ContainsKey(mat))
                {
                    texMaps.Add(mat, index);
                    index++;
                }
            }
        }
        public CameraSample GetCameraSample(float x, float y, IRandomProvider sampler)
        {
            var res = new CameraSample()
                {
                    imageX = x,
                    imageY = y,
                };
            if (sampler != null)
            {
                res.lensU = sampler.NextFloat();
                res.lensV = sampler.NextFloat();
                res.time = sampler.NextFloat();
            }
            IRay ray;
            this.Camera.GetRay(x, y, out ray);
            res.EyeRay = (RayData)ray;
            return res;
        }

        
        public ITexture Query(string material, TextureType tex)
        {
            if (texMaps == null)
            {
                texMaps = new Dictionary<string, int>();
                int index = 0;
                foreach (var elem in texturesMap)
                {
                    var mat = MatLib.GetSurfMat(index).Name;
                    if (!texMaps.ContainsKey(mat))
                    {
                        texMaps.Add(mat.ToLower(), index);
                        index++;
                    }
                }
            }
            return this.OptTextures[texturesMap[texMaps[material.ToLower()]][tex]];
        }

        public IScenePrimitive GetPrimitive(uint index)
        {
            return primitives.GetPrimitive(index);
        }

        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public ITexture Query(int materialID, TextureType tex)
        {
            if (texturesMap.Length <= materialID || texturesMap[materialID] == null)
                return null;
            if (!texturesMap[materialID].ContainsKey(tex))
                return null;
            return this.OptTextures[texturesMap[materialID][tex]];
        }

        internal VolumeComputation GetVolumeComputation()
        {
            return new VolumeComputation(this.VolumeIntegrator);
        }


        public ITriangleMesh GetMeshByTriangleIndex(int currentTriangleIndex)
        {
            if (currentTriangleIndex > sceneData.Triangles.Length)
                return null;
            return sceneData.GetMeshByTriangleIndex(currentTriangleIndex);
            /*
            ITriangleMesh m;
            meshTriangleMap.TryGetValue(currentTriangleIndex, out m);
            if (!m.TriangleInRange(currentTriangleIndex))
            {
                Debugger.Break();
            }
            return m;
            for (int index = 0; index < Meshes.Count; index++)
            {
                if (Meshes[index].TriangleInRange(currentTriangleIndex))
                    return Meshes[index];
            }
            return null;*/
        }


        public bool IsVolumeTriangle(int currentTriangleIndex)
        {
            if (noVolumes)
                return false;
            return volumeIndex.Contains(currentTriangleIndex);
        }

        public bool IsVolumeMaterial(int id)
        {
            return false;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool IsLight(int currentTriangleIndex)
        {
            if (noMeshLights)
                return false;

            if (AreaLightAsMesh)
            {
                /*if (lightIndex == null || lightIndex.Count == 0)
                {
                    lightIndex = new HashSet<int>(lightMeshes.SelectMany(m=>Enumerable.Range(m.StartTriangle, m.TrianglesCount)));
                }

                return lightIndex.Contains(currentTriangleIndex);*/

                for (int index = 0; index < lightMeshes.Length; index++)
                {
                    //if (Meshes[index].TriangleInRange(currentTriangleIndex, 1) && (Meshes[index].MeshProfile is LightsourceProfile))
                    if (lightMeshes[index].TriangleInRange(currentTriangleIndex, 1))
                        return true;
                }
            }
            else
            {
                return this.lightIndex.Contains(currentTriangleIndex);
                for (int index = 0; index < triLights.Length; index++)
                {
                    var light = triLights[index];
                    {
                        if ((light).triangleIndex == currentTriangleIndex)
                            return true;
                    }
                }
            }
            return false;
        }

        public ILight GetLightByIndex(int currentTriangleIndex)
        {
            if (AreaLightAsMesh)
            {
                for (int index = 0; index < Meshes.Length; index++)
                {
                    if (Meshes[index].TriangleInRange(currentTriangleIndex))
                    //&&(Meshes[index].MeshProfile is LightsourceProfile))
                    {

                        var profileLight = FindLight(currentTriangleIndex);
                        return profileLight;
                    }
                }
            }
            else
            {
                for (int index = 0; index < Lights.Length; index++)
                {
                    var light = Lights[index];
                    if (light is TriangleLight)
                    {
                        if ((light as TriangleLight).triangleIndex == currentTriangleIndex)
                            return light;
                    }
                }
            }
            return null;
        }

        public int SampleLights(float p, out float lightStrategyPdf)
        {
            int lightIndex = Math.Min((int)Math.Floor(Lights.Length * p), Lights.Length - 1);
            lightStrategyPdf = 1f / Lights.Length;
            return lightIndex;
        }

        public int SampleLights(float p)
        {
            int lightIndex = Math.Min((int)Math.Floor(Lights.Length * p), Lights.Length - 1);
            return lightIndex;
        }

        public RgbSpectrum SampleEnvironment(Vector dir)
        {
            //return RgbSpectrum.ZeroSpectrum();
            if (EnvironmentMap != null)
            {
                return (RgbSpectrum)(EnvironmentMap.SampleBackground(ref dir));
            }
            return RgbSpectrum.ZeroSpectrum();
            //if (dir.y > 0)
            //    return skyEmission;
            //return groundReflection;
        }
        public RgbSpectrum SampleEnvironment(ref Vector dir)
        {
            //return RgbSpectrum.ZeroSpectrum();
            if (EnvironmentMap != null)
            {
                return (RgbSpectrum)(EnvironmentMap.SampleBackground(ref dir));
            }
            return RgbSpectrum.ZeroSpectrum();
            //if (dir.y > 0)
            //    return skyEmission;
            //return groundReflection;
        }

        ILight FindLight(int triangleIndex)
        {
            foreach (var light in Lights.OfType<MeshLight>())
            {
                return light;
            }
            return this.Lights.OfType<TriangleLight>().FirstOrDefault(item => item.triangleIndex.Equals(triangleIndex));
        }

        public void BuildPrimitives()
        {
            primitives = new PrimitiveCache(this);
        }
    }
}
